import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.NoSuchElementException;
import java.util.Scanner;

/*
 * CS151 Group 1
 * Mancala AI Player Code
 * 
 * Mohammad Noorzay, Daryl Hennegan, Justin Turpin, Robert Rodriguez, Peter Huynh
 */
public class group1x_Player implements MancalaPlayer {
	public group1x_KalahHeuristic heuristic;
	public int player_number;
	public boolean did_pie = false;

	public static final int DEPTH = 7;
	private double nodesExplored;
	private double betaPrunes;
	private double alphaPrunes;
	private double winningNode;
	private double losingNode;
	private double calculationTime;
	private HashMap<String, StateSimple> stateTable;
	private ArrayList<String> moveList;
	private String fileName = "State_Values2.txt";

	
	
	public static int max( int a, int b) {
		if( a > b ) {
			return a;
		}
		return b;
	}
	
	public static int min( int a, int b ) {
		if( a < b){
			return a;
		}
		return b;
	}
	
	public group1x_Player( int player_number ) {
		this.player_number = player_number;

		calculationTime = 0;
		nodesExplored = 0;
		betaPrunes = 0;
		alphaPrunes= 0;
		winningNode = 0;
		losingNode = 0;
		heuristic = new group1x_KalahHeuristic( );
		stateTable = new HashMap<String, StateSimple>();
		moveList = new ArrayList<String>();
		
		File file = new File(fileName);
		if (file.exists()) {
			try {
				Scanner reader = new Scanner (new FileReader(file));
				while (reader.hasNext()) {
					stateTable.put(reader.next(), new StateSimple(Integer.valueOf(reader.next()), Double.valueOf(reader.next())));
				}
//				System.out.println("STATE TABLE");
//				for (String s : stateTable.keySet()) {
//					System.out.println(s);
//				}
				reader.close();
			} catch (FileNotFoundException e) {
				System.err.println("Reader failed during player construction.");
				e.printStackTrace();
			} catch (NoSuchElementException e) {
				System.err.println("Reader failed during reading during player construction");
				e.printStackTrace();
			}
		}
	}
	
	public void setHeuristic( group1x_KalahHeuristic heuristic ) {
		this.heuristic = heuristic;
	}
	
	@Override
	public int getMove(MancalaGameState gs) throws Exception {
		
		double time = System.currentTimeMillis()/1000.0;
		String chosen = null;
		
		group1_KalahAction test = get_maximum_action( (KalahPieGameState)gs, DEPTH, Integer.MIN_VALUE, Integer.MAX_VALUE);
		if( test.game_move ==  -1 ) {
			this.did_pie = true;
		}
		time = System.currentTimeMillis()/1000.0 - time;
		
		calculationTime+=time;
//		System.out.println("Score: " + test.score);
//		System.out.format("%.3f seconds to calculate move.\n", time);
//		System.out.println( "AI (Player " + this.player_number + ") Move: " + test.game_move );
		KalahPieGameState resultingState = (KalahPieGameState) gs.copy();
		resultingState = (KalahPieGameState) resultingState.play(test.game_move);
//		System.out.println("resulting");
//		resultingState.printState();
//		System.out.println("test");
//		test.state.printState();
		if (test.state != null) {
			chosen = StateSimple.codifyState(test.state, 1 - player_number);
//			System.out.println(chosen);
		}
		else { // in this case, just getting started
			chosen = StateSimple.START_STATE;
		}
		moveList.add(chosen);
//		Random random = new Random(date.getTime());
//		if (random.nextInt(10) > 8) { // %10 random move
//			int nextMove = random.nextInt(6);
//			while (!gs.validMove(nextMove))
//				nextMove = random.nextInt(6);
//			return nextMove;
//		}
		return test.game_move;
	}
	
	/*
	 * Returns the action that yields the highest score for the current player.
	 * Depth should be a positive number. When depth is 0 then the function will
	 * no longer recurse.
	 */
	public group1_KalahAction get_maximum_action( KalahPieGameState state, int depth, int alpha, int beta ) throws Exception {
		
		boolean isEndGame = state.checkEndGame();
		
		if( depth == 0 || isEndGame ) {
			// This is the base case if there are no child nodes (an end game) or we have hit the depth limit.
			group1_KalahAction action = new group1_KalahAction();
			action.state = state;
			action.score = heuristic.evaluate(state, this.player_number, stateTable );
			if (isEndGame){
				KalahPieGameState endGame = (KalahPieGameState) state.copy();
				endGame.computeFinalScore();
				int player_score = endGame.score[ player_number ];
				int enemy_score  = endGame.score[ 1 - player_number ];
				
				if( player_score > enemy_score )
				{
					winningNode++;
				}
				else
				{
					losingNode++;
				}
			}
			nodesExplored++;
			return action;
		} else {
			// Get all the child states for this given state
			group1_KalahAction child_actions[] = group1_KalahAction.get_possible_actions(state);
			group1_KalahAction peak_action     = null;
			for( int i = 0; i < child_actions.length; i++ ) {
				// Call this function recursively, getting the maximum of a child action.
				group1_KalahAction current_action = get_maximum_action( child_actions[i].state, depth - 1, alpha, beta );
				
				// Don't propagate the game move up because we don't want the move that results in the deepest game state.
				current_action.game_move   = child_actions[i].game_move;
				// This is our maximizing code
				if( peak_action == null ) {
					peak_action = current_action;
				} else {
					// If the game state that we're looking at is our player's turn, then maximize the child scores.
					// Otherwise, minimize the child scores.
					if( this.player_number == state.CurrentPlayer ) {
						if( current_action.score > peak_action.score ) 
							peak_action = current_action;
					} else {
						if( current_action.score < peak_action.score ) 
							peak_action = current_action;
					}
					
					// Perform alpha beta pruning
					if( this.player_number == state.CurrentPlayer ) {
						alpha = max( alpha, current_action.score );
						if( beta <= alpha ) {
							alphaPrunes++;
							break;
						}
					} else {
						beta = min( beta, current_action.score );
						if( beta <= alpha ) {
							betaPrunes++;
							break;
						}
					}
				}
			}
			
			return peak_action;
		}
	}

	@Override
	public Object postGameActions(MancalaGameState gs) {
		
		int stateValue = 0;
		
		if( gs.getScore( player_number ) > gs.getScore( 1 - player_number ) ) {
			heuristic.wins += 1.0;
			stateValue = 1;
		} else if(  gs.getScore( player_number ) == gs.getScore( 1 - player_number ) ) {
			heuristic.wins += 0.5;
		}else {
			heuristic.losses++;
			stateValue = -1;
		}
		
		for (String move : moveList) {
//			System.out.println(move);
			if (stateTable.containsKey(move)) {
				stateTable.get(move).value = (stateTable.get(move).value * stateTable.get(move).age + (double) stateValue) / (stateTable.get(move).age + 1);
				//Expand old average by multiplying by games played, add new value, then find new average by dividing sum by games played + 1.
				stateTable.get(move).age++;
			}
			else {
				stateTable.put(move, new StateSimple(1, (double) stateValue)); 
			}
		}
		heuristic.gamesPlayed++;
		
//		if( false ) {
//			System.out.format("Player [%d] named %d score: %s%n", player_number, heuristic.name, gs.getScore(0));
//			System.out.println("Depth: "+ DEPTH);
//			System.out.format("Total Calculation time: %.4f%n", calculationTime);
//			System.out.format("Alpha Prunes: %.0f%n" , alphaPrunes);
//			System.out.format("Beta Prunes: %.0f%n"  , betaPrunes);
//			System.out.format("Nodes Explored: %.0f%n" , nodesExplored);
//			System.out.format("Winning Nodes Explored: %.0f%n" , winningNode);
//			System.out.format("Losing Nodes Explored: %.0f%n" , losingNode);
//			System.out.println(did_pie ? "Used Pie Move!" :"");
//			System.out.println();
//		}
		
		
		try {
			BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
			for (String key : stateTable.keySet()) {
				writer.write(key);
				writer.write(" " + String.valueOf(stateTable.get(key).age) + " " + 
						String.valueOf(stateTable.get(key).value));
				writer.newLine();
			}
			writer.close();
		} catch (IOException e) {
			System.err.println("Writer at before Deletion Had an Exception");
			e.printStackTrace();
		}
		
		return null;
	}

	@Override
	public Object actionsBeforeDeletion() {
		File file = new File(fileName);
		if (file.exists()) {
			file.delete();
		}

		return null;
	}
}
